package com.example.springboottest.example.threadpoll;

import java.util.concurrent.*;

/**
 * 什么时候使用线程池?
 * 单个任务处理时间比较短；需要处理的任务数量很大。
 * <p>
 * Executor 又有两个子接口：ExecutorService 和 ScheduledExecutorService，常用的接口是 ExecutorService。
 * <p>
 * ExecutorServcie和ScheduledExecutorService都是java.util.Concurrent包的接口，
 * 但scheduledExecutorService提供了一些额外的方法来执行具有延迟或每个固定时间段的Runnable和Callable任务。
 * <p>
 * 同时常用的线程池的工具类叫 Executors
 * <p>
 * ExecutorService和ThreadPoolExecutor 关系
 * ThreadPoolExecutor 继承ExecutorService *
 * <p>
 * Executors 工具类默认的一些实现 new 出来的线程池都是用的 ThreadPoolExecutor 线程执行器这个类进行构造的，
 * 不过参数不同，导致了效果的侧重点不同。
 * 但是一般建议自行构造ThreadPoolExecutor，而不是通过内置Executors生产，可以修改内在参数
 * 自己创建线程池推荐的方法就是，直接使用 ThreadPoolExecutor 进行个性化的创建
 * <p>
 * 自行构造线程池
 * new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler)
 * 构造方法种的参数有 7 个：
 * 1、corePoolSize：线程池维护线程的最少数量 （core : 核心）
 * 2、maximumPoolSize：线程池维护线程的最大数量，显然必须>=1
 * 3、keepAliveTime：线程池维护的多余的线程所允许的空闲时间，最长可以空闲多久，时间到了，如果超过 corePoolSize 的线程一直空闲，他们就会被销毁。
 * 4、unit：线程池维护线程所允许的空闲时间的单位
 * 5、workQueue：线程池所使用的缓冲队列，已经提交但是没有执行的任务会放进这里
 * 6、threadFactory：生成线程池种工作线程的线程工厂，一般使用默认
 * 7、handler：线程池对拒绝任务的处理策略，当队列满且工作线程已经达到maximumPoolSize。
 * <p>
 * 线程池的五种状态
 * 1、Running，能接受新任务以及处理已添加的任务；
 * 2、Shutdown，不接受新任务，可以处理已经添加的任务，也就是不能再调用execute或者submit了；
 * 3、Stop，不接受新任务，不处理已经添加的任务，并且中断正在处理的任务；
 * 4、Tidying，所有的任务已经终止，CTL记录的任务数量为0，CTL负责记录线程池的运行状态与活动线程数量；
 * 5、Terminated，线程池彻底终止，则线程池转变为terminated的状态。
 * 从running状态转换为 shutdown，调用 shutdown()方法；如果调用shutdownNow()方法，就直接会变成stop
 * <p>
 * 阻塞队列
 * 线程池里的 BlockingQueue，阻塞队列，事实上在消费者生产者问题里的管程法实现，我们的策略也是类似阻塞队列的，用它来做一个缓存池的作用。
 * 阻塞队列：任意时刻，不管并发有多高，永远保证只有一个线程能够进行队列的入队或出队操作。也就意味着他是能够保证线程安全的。
 * 另外，阻塞队列分为有界和无界队列，理论上来说一个是队列的size有固定，另一个是无界的。对于有界队列来说，如果队列存满，只能出队了，入队操作就只能阻塞。
 * 在 juc 包里，阻塞队列的实现有很多：
 * 1、ArrayBlockingQueue：有界阻塞队列；
 * 2、LinkedBlockingQueue：链表结构（大小默认值为Integer.MAX_VALUE）的阻塞队列；
 * 3、PriorityBlockingQueue：支持优先级排序的无界阻塞队列；
 * 4、DelayQueue：使用优先级队列实现的延迟无界阻塞队列；
 * 5、SynchronousQueue：不存储元素的阻塞队列，相当于只有一个元素；
 * 6、LinkedTransferQueue：链表组成的无界阻塞队列；
 * 7、LinkedBlockingDeque：链表组成的双向阻塞队列。
 */
public class ExecutorServcieMain {
    public static void main(String[] args) {
        /**
         * Executor 框架虽然提供了如
         *  newFixedThreadPool()
         *  newSingleThreadExecutor()
         *  newCachedThreadPool()
         *  newScheduledThreadPool() 等创建线程池的方法，但都有其局限性，不够灵活
         */

        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        /**
         * 单线程的线程池,简单线程执行器,适用于执行长期任务的场景
         * 等价于
         * new ThreadPoolExecutor(1, 1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())
         */

        ExecutorService fixedThreadExecutor = Executors.newFixedThreadPool(5);
        /**
         * 固定的线程池，传参就是线程固定数目，适用于执行长期任务的场景，缺点没有扩展性
         * 等价于
         * new ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())
         */

        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        /**
         * 缓存线程池，核心线程0，最大线程非常大,动态创建的特点,缺点最大线程数太大，内存有爆的风险
         * 等价于
         * new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>())
         */

        ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        /**
         * 时间安排线程池，指定核心线程数,缺点最大线程数太大，内存有爆的风险
         * 等价于
         * new ThreadPoolExecutor(corePoolSize, Integer.MAX_VALUE,0, NANOSECONDS,new DelayedWorkQueue<Runnable>())
         */

        /**
         * 线程池响应不了请求任务，拒绝的例子
         */
        rejectedExecute();
    }

    /**
     * 线程池对拒绝任务的处理策略，当队列满且工作线程已经达到maximumPoolSize
     * 故意把阻塞队列设置为3，容易出现拒绝的现象
     * <p>
     * 第7个参数handler：线程池对拒绝任务的处理策略，当队列满且工作线程已经达到maximumPoolSize。
     * 有4种策略
     * 1、CallerRunsPolicy：调用者运行策略
     * 这种策略不会抛弃任务，也不抛出异常，而是将某些任务回退给调用者，从而降低新任务的流量。
     * 2、AbortPolicy：终止策略
     * 默认的拒绝策略。
     * 3、DiscardPolicy：丢弃任务
     * 可以看到，源码里就是是什么也不做。如果场景中允许任务丢失，这个是最好的策略。
     * 4、DiscardOldestPolicy：抛弃队列中等待最久的任务
     * 抛弃队列中等待最久的任务，然后把当前的任务加入队列中，尝试再次提交当前任务。
     */
    public static void rejectedExecute() {
        ExecutorService rejectedThreadPool = new ThreadPoolExecutor(
                2,
                5,
                2L,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        try {
            //模拟10个顾客办理业务
            for (int i = 0; i < 10; i++) {
                //execute 执行方法，传入参数为实现了 Runnable 接口的类
                rejectedThreadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "号线程办理业务");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rejectedThreadPool.shutdown();
        }
    }
}
